package ue.client.networking;

import java.io.DataInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;

import javax.crypto.spec.SecretKeySpec;
import javax.swing.JOptionPane;

import ue.client.core.Game;
import ue.client.core.Util;
import ue.client.galaxy.Asteroid;
import ue.client.galaxy.Location;
import ue.client.planes.Subspace;
import ue.client.resources.Deposit;

public class ClientInputListener implements Runnable {
	private DataInputStream in;
	private Client client;
	
	public ClientInputListener(DataInputStream in, Client client) {
		this.in = in;
		this.client = client;
	}
	
	@Override
	public void run() {
		for(;;) {
			byte command = receiveCommand();
			System.out.println(Game.getTimestamp() + "Got Command " + command + " from the Captain.");
			
			switch(command) {
			case 0:
				ping();
				break;
			case 1:
				System.out.println(Game.getTimestamp() + "Identifying to the Captain.");
				identify();
				break;
			case 2:
				System.out.println(Game.getTimestamp() + "Identification successful!");
				client.identified = true;
				break;
			case 3:
				System.out.println(Game.getTimestamp() + "Identification unsuccessful");
				JOptionPane.showMessageDialog(null, "Server refused Connection", "Connection Failure", JOptionPane.ERROR_MESSAGE);
				System.exit(0);
				break;
			case 4:
				System.out.println(Game.getTimestamp() + "Receiving Location from the Captain.");
				Location location = receiveLocation();
				System.out.println(location);
				
				Subspace.locations.add(location);
			}
			
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	private void ping() {
//		System.out.println(System.currentTimeMillis() + ": Paying Taxes.");
		client.sendCommand((byte)0);
	}
	
	private void identify() {
		BigInteger p = receiveBigInteger();
		
		BigInteger g = receiveBigInteger();
		
		BigInteger a = Cryptography.generateDHPrivateNumber(p);
		
		BigInteger A = Cryptography.calculateDHTransferNumber(a, p, g);
		
		
		BigInteger B = receiveBigInteger();
		
		client.sendBigInteger(A);
		
//		System.out.println("A: " + A);
//		System.out.println("B: " + B);
//		System.out.println("a: " + a);
//		System.out.println("p: " + p);
//		System.out.println("g: " + g);
		BigInteger key = Cryptography.calculateDHKey(B, a, p);
//		System.out.println(key);
		
		SecretKeySpec keySpec = Cryptography.generateAESKey(key);
		
		MessageDigest md = null;
		try {
			md = MessageDigest.getInstance("SHA-256");
		} catch (NoSuchAlgorithmException e1) {
			e1.printStackTrace();
		}
		
		md.update(client.password.getBytes());
		byte[] pwHash = md.digest();
		
		byte[] nameEncrypted = Cryptography.encodeAES(keySpec, client.name.getBytes());
		byte[] passwordEncrypted = Cryptography.encodeAES(keySpec, pwHash);
		
		try {
			client.out.write(nameEncrypted);
			client.out.write(passwordEncrypted);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public Location receiveLocation() {
		int x = 0, y = 0;
		String name = "";
		double sunFactor = 0.0;
		int asteroidAmount = 0;
		double asteroidValue = 0.0;
		ArrayList<Asteroid> asteroids = new ArrayList<Asteroid>();
		
		try {
			x = in.readInt();
			y = in.readInt();
			name = Util.generateName(Location.generateSeed(x, y), 0);
			sunFactor = in.readDouble();
			asteroidAmount = in.readInt();
			asteroidValue = in.readDouble();
			for(int i = 0; i < asteroidAmount; i++) {
				asteroids.add(receiveAsteroid());
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		
//		System.out.println(x + ":" + y + ":" + Location.generateSeed(x, y) + ":" + name);
		
		return new Location(x, y, name, sunFactor, asteroidAmount, asteroidValue, asteroids);
	}
	
	public Asteroid receiveAsteroid() {
		int duration = 0;
		double value = 0.0;
		int mainResource = 0;
		Deposit deposit = null;
		
		try {
			duration = in.readInt();
			value = in.readDouble();
			mainResource = in.readInt();
			deposit = receiveDeposit();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		return new Asteroid(duration, value, mainResource, deposit);
	}
	
	public Deposit receiveDeposit() {
		int length = 0;
		try {
			length = in.readInt();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
//		System.out.println(length);
		
		int[] resources = new int[length];
		
		try{
			for(int i = 0; i < length; i++) {
				resources[i] = in.readInt();
			}
		} catch(IOException e) {
			e.printStackTrace();
		}
	
		
		return new Deposit(resources);
	}
	
	protected byte receiveCommand() {
		byte[] bytes = new byte[1];
		try {
			in.read(bytes);
		} catch (IOException e) {
			e.printStackTrace();
			JOptionPane.showMessageDialog(null, "Connection to Server lost.", "Connection Lost", JOptionPane.ERROR_MESSAGE);
			System.exit(1);
		}
		return bytes[0];
	}
	
	protected byte[] receiveBytes(int length) {
		byte[] bytes = new byte[length];
		try {
			in.read(bytes);
		} catch (IOException e) {
			e.printStackTrace();
		}
		return removeZeros(bytes);
	}
	
	private BigInteger receiveBigInteger() {
		byte[] bytes = new byte[64];
		try {
			in.read(bytes);
		} catch (IOException e) {
			e.printStackTrace();
		}
		return new BigInteger(removeZeros(bytes));
	}
	
	private byte[] removeZeros(byte[] bytes) {
		if(bytes.length == 1) return bytes;
		
		while(bytes[bytes.length - 1] == 0) {
			bytes = Arrays.copyOf(bytes, bytes.length - 1);
		}
		
		return bytes;
	}
}